"""Provides the ServerThread and the LoopThread classes."""
import logging
import time
from Queue import Empty
import sys
import datetime
import math
import threading
import Queue
import os
from PyQt4.QtCore import *

from misc import misc
from misc import parts

class NavalaThread(threading.Thread, parts.Sleeper):
    pass

class ServerThread(NavalaThread, QObject):
    """Implements a blocking Start() method and an abstract _Exit() method.
    
    This thread is suitable for running a blocking statement, such as a Pyro
    daemon.
    
    The _Exit() method must be reimplemented to poke the daemon to finish. The
    exit mechanism is expected to be specific to the task that a descendant
    class is implementing.
    
    The Start() methods is implemented using Condition so that it waits until
    run_() has produced a notification that it is alright, or an Exception is
    caught by run(). When Start() returns, either flagStarted or flagError
    is set.
    
    """

    flagStarted = False
    flagError = False
    #error = None
    #traceback = None
    exc_type = None
    exc_value = None
    exc_traceback = None

    def __init__(self, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)
        QObject.__init__(self)
        self.L = threading.Lock()
        self.C = threading.Condition(self.L)

        self.logFilename = misc.slugify("t_"+self.name)+".log"
        self.logger = misc.GetLogger(self.name, self.logFilename)
    
    def AssertStarted(self):
        if not self.is_alive():
            self.Start()
    
    def Start(self):
        """This start blocks until either flagStarted or flagError is True.
        
        If flagError is true, it will *raise* the error. So, effectively,
        this method will raise an exception if the thread somehow fails to
        start."""

        self.logger.debug("Server Thread ``%s`` waiting for SetStarted()" % self.name)        
        with self.C:
            self.start()
            while not (self.flagStarted or self.flagError):
                self.C.wait() # Releases lock and waits for C.notify()
                
        if self.flagError:
            raise self.exc_type, self.exc_value, self.exc_traceback
            
        self.logger.debug("Server Thread ``%s`` Start() END" % self.name)
                
    def SetStarted(self):
        self.flagStarted = True
        self.C.notify()
        self.C.release()

    def run(self):
        self.C.acquire()
        try:
            self.run_()
            if self.L.locked():
                raise RuntimeError("Implementation error! run_() method finished smoothly but didn't call SetStarted()!")
            self.logger.info("FFFFFFFFFF Thread ``%s`` finished normally" % self.name)
        except:
            # It may happen that in the end of run(), the thread object will
            # have both flagStarted and flagError set to true, but only one
            # of these is required to release the waiting Start() method."""
            self.logger.exception("Caught Exception")
            self.flagError = True
            self.exc_type, self.exc_value, self.exc_traceback = sys.exc_info()
            if self.L.locked():
                self.C.notify()
                self.C.release()
                self.logger.debug("L was Locked")
            
    def run_(self):
        """Abstract method. DON'T forget to call self.SetStarted()*
        
        * If self.SetStarted() is not called, the thread will start running and Start()
        will wait forever (or until run_() gives an error). This makes complete sense,
        since it is YOUR responsibility to notify WHEN the initialization has finished.
        """
        raise NotImplementedError("Not implemented")

    def Exit(self):
        self.logger.info(">>>>>>>>>>>>>>>> ``%s`` TRYING TO EXIT..." % self.name)
        try:
            self._Exit()
            self.logger.info("<<<<<<<<<<<<<<<< ``%s`` EXITED SUCCESSFULLY!!!" % self.name)
        except:
            self.logger.info("|*|*|*|*|*|*|*|* ``%s`` fucked up trying to exit" % self.name)

    def _Exit(self):
        """Must reimplement this."""
        raise NotImplementedError("Not implemented")


 
################################################################################

class LoopThread(NavalaThread, QObject):
    """Implements add-ons to facilitate the implementation of a looping Thread.
    
    This is somewhat similar to NavalaProcess
    
    Communication is done through "messages". A message consists of a token, data
    pair. PutMessage() is used to put an arbitrary message.
    
    LoopThread treats the following tokens inside TreatQueueItem():
        exit
        ping - emits a Qt signal "Pong"
        
    Remember that exceptions raised from _Cycle() will cause the thread to exit!
    """
    # To compose log messages
    dash = "--------" 
    # Don't set this directly, this is made inside TreatQueueItem()
    flagExit = False
    # This is initialized internally
    inQueue = None
    # Whether there was an unhandled error inside run()
    flagError = False
    # Corresponding exception if flagError is set to True
    error = None

    Pong = pyqtSignal((str))
    ExitedWithError = pyqtSignal()
    ExitedOk = pyqtSignal()

    def __init__(self, *args, **kwargs):
        """Agent initialization passing name and data.
        
        Passed data is merged with the a dictionary already containing the
        "state" key.
        
        """
        threading.Thread.__init__(self, *args, **kwargs)
        QObject.__init__(self)
        self.inQueue = Queue.Queue()
        
        self.logFilename = misc.slugify("t_"+self.name)+".log"
        self.logger = misc.GetLogger(self.name, self.logFilename)
        
    def Start(self):
        self.start()
        
    def PutMessage(self, token, msg=True):
        """Puts an arbitrary message. To be called by any thread."""
        if not token[0].isupper():
            raise RuntimeError("Invalid token: ``%s`` (must be CamelCase with first letter uppercase)" % token)
        self.inQueue.put((token, msg))
    
    def Exit(self):
        """Puts an "exit" message. To be called by parent process."""
        logging.info("%s (%s): Exit()" % (self.name, self.__class__.__name__))
        self.PutMessage("Exit")
        
    def Ping(self, msg="hi"):
        """Puts a "ping" message. To be called by parent process."""
        self.PutMessage("Ping", msg)
    
    def ReadQueue(self):
        """Include a call to this function in the reimplementation of run_()."""
        flagEmpty = False
        while not flagEmpty:
            try:
                t = self.inQueue.get(False)
                self.TreatQueueItem(t)
            except Empty:
                flagEmpty = True

    def TreatQueueItem(self, t):
        """Treats messages got from inQueue. Reimplement _TreatQueueItem()."""
        if not isinstance(t, tuple):
            t = (t, True)
        self.logger.info("Received message \"%s\" with data=%s" % (t[0], repr(t[1])))
        self._TreatQueueItem(t[0], t[1])
        

    def _TreatQueueItem(self, token, data):
        """Treats "exit", and "ping". Reimplement to treat more tokens.
        
        This method should be reimplemented to treat additional tokens. In the
        reimplementation, call the superclass method if the token is left
        untreated"""
        if token == "Exit":
            self.flagExit = data
        elif token == "Ping":
            s = "You said %s" % data
            self.Pong.emit(s)
            self.logger.info("PINGED: "+s)
        else:
            self.logger.warning("Unknown token: \"%s\"" % token)

    def run(self):
        self.logger.info("")
        self.logger.info("%s Thread \"%s\" started at %s %s" % (self.dash, 
            self.name, datetime.datetime.today().strftime("%Y/%m/%d %H:%M"), self.dash))
            
        try: # Puts main loop in a try...except block to log uncaught exceptions
            while True:
                self.ReadQueue()
                if self.flagExit:
                    self.logger.info(">>>>>>>> %s ``%s`` TOLD TO EXIT %s" % (self.dash, self.name, self.dash))
                    self._BeforeExit()
                    break
                self._Cycle()
        except Exception as E:
            self.logger.exception("|*|*|*|* %s ``%s`` HAD NAUGHTY EXIT %s" % (self.dash, self.name, self.dash))
            self.flagError = True
            self.error = E
            raise # Not sure if I need to raise this, since it is the end of run()
        self.logger.info("<<<<<<<< %s ``%s`` EXITED NORMALLY  %s" % (self.dash, self.name, self.dash))
            

    def _Cycle(self):
        """Implement this for your own logic; probably put a sleep() inside"""
        pass

    def _BeforeExit(self):
        """Reimplement to perform some cleanup before the thread exits."""
        pass

